﻿// -------------------------------------------------------------------------------------------------------------------------------
// <copyright file="TestAssignIndexedRMDenseMatrix.cs" company="Exul">
//     Copyright (c) Exul. All rights reserved.
// </copyright>
// <author>
//     Exul
// </author>
// -------------------------------------------------------------------------------------------------------------------------------

namespace ExulLibrary.Mathematics.LinearAlgebra.Sequential.Tests.CrsSparseMatrixTest
{
    #region Usings

    using System;
    using System.Globalization;

    using ExulLibrary.Exceptions;
    using ExulLibrary.Mathematics.LinearAlgebra.Exceptions;

    using NUnit.Framework;

    #endregion

    /// <summary>
    ///     Test suite for <see cref="CrsSparseMatrixExtensions.Assign{T}(CrsSparseMatrix{T},int[],int[],RMDenseMatrix{T})"/>.
    /// </summary>
    [TestFixture]
    [Category("ExulLibrary.Mathematics.LinearAlgebra.Sequential.CrsSparseMatrixExtensions")]
    public sealed class TestAssignIndexedRMDenseMatrix
    {
        /// <summary>
        ///     Tests the method when indexes specify existing elements.
        /// </summary>
        [Test]
        public void WhenTargetIndexesAreValid()
        {
            // Arrange
            // Matrix:
            // 1(Y) 0 2 8(Y)
            // 3    4 0 0
            // 5(Y) 0 0 7(Y)
            var target = new CrsSparseMatrix<double>(
                rowsCount: 3,
                columnsCount: 4,
                rowsBegins: new[] { 0, 3, 5, 7 },
                columnsIndexes: new[] { 0, 2, 3, 0, 1, 0, 3 },
                data: new double[] { 1, 2, 8, 3, 4, 5, 7 });

            var targetRowsIndexes = new[] { 0, 2 };
            var targetColumnsIndexes = new[] { 0, 3 };

            var source = new RMDenseMatrix<double>(
                new[]
                {
                    new double[] { 10, 20 },
                    new double[] { 30, 40 }
                });

            // Act
            target.Assign(targetRowsIndexes, targetColumnsIndexes, source);

            // Assert
            Assert.That(target.Values, Is.EquivalentTo(new[] { 10, 2, 20, 3, 4, 30, 40 }));
        }

        /// <summary>
        ///     Tests the method when targetRowsIndexes parameter is empty collection.
        /// </summary>
        [Test]
        public void WhenRowsIndexesCollectionIsEmpty()
        {
            // Arrange
            // Matrix:
            // 1 0 2 8
            // 3 4 0 0
            // 5 0 0 7
            var target = new CrsSparseMatrix<double>(
                rowsCount: 3,
                columnsCount: 4,
                rowsBegins: new[] { 0, 3, 5, 7 },
                columnsIndexes: new[] { 0, 2, 3, 0, 1, 0, 3 },
                data: new double[] { 1, 2, 8, 3, 4, 5, 7 });

            var targetRowsIndexes = new int[0];
            var targetColumnsIndexes = new[] { 0, 3 };

            var source = new RMDenseMatrix<double>(
                new[]
                {
                    new double[] { 10, 20 },
                    new double[] { 30, 40 }
                });

            // Act
            target.Assign(targetRowsIndexes, targetColumnsIndexes, source);

            // Assert
            Assert.That(target.Values, Is.EquivalentTo(new[] { 1, 2, 8, 3, 4, 5, 7 }));
        }

        /// <summary>
        ///     Tests the method when targetColumnsIndexes parameter is empty collection.
        /// </summary>
        [Test]
        public void WhenColumnsIndexesCollectionIsEmpty()
        {
            // Arrange
            // Matrix:
            // 1 0 2 8
            // 3 4 0 0
            // 5 0 0 7
            var target = new CrsSparseMatrix<double>(
                rowsCount: 3,
                columnsCount: 4,
                rowsBegins: new[] { 0, 3, 5, 7 },
                columnsIndexes: new[] { 0, 2, 3, 0, 1, 0, 3 },
                data: new double[] { 1, 2, 8, 3, 4, 5, 7 });

            var targetRowsIndexes = new[] { 0, 2 };
            var targetColumnsIndexes = new int[0];

            var source = new RMDenseMatrix<double>(
                new[]
                {
                    new double[] { 10, 20 },
                    new double[] { 30, 40 }
                });

            // Act
            target.Assign(targetRowsIndexes, targetColumnsIndexes, source);

            // Assert
            Assert.That(target.Values, Is.EquivalentTo(new[] { 1, 2, 8, 3, 4, 5, 7 }));
        }

#if !SAFE

        /// <summary>
        ///     Tests the method when indexes specify an element not in portrait of the target matrix.
        /// </summary>
        [Test]
        public void WhenTargetIndexesSpecifyAnElementNotInPortrait()
        {
            // Arrange
            // Matrix:
            // 1(Y) 0 2(Y) 8
            // 3    4 0    0
            // 5(Y) 0 0(Y) 7
            var target = new CrsSparseMatrix<double>(
                rowsCount: 3,
                columnsCount: 4,
                rowsBegins: new[] { 0, 3, 5, 7 },
                columnsIndexes: new[] { 0, 2, 3, 0, 1, 0, 3 },
                data: new double[] { 1, 2, 8, 3, 4, 5, 7 });

            var targetRowsIndexes = new[] { 0, 2 };
            var targetColumnsIndexes = new[] { 0, 2 };

            var source = new RMDenseMatrix<double>(new[]
            {
                new double[] { 10, 20 },
                new double[] { 30, 40 }
            });

            // Act
            target.Assign(targetRowsIndexes, targetColumnsIndexes, source);

            // Assert
            Assert.That(target.Values, Is.EquivalentTo(new[] { 10, 20, 8, 3, 4, 30, 7 }));
        }

#endif

#if SAFE

        /// <summary>
        ///     Tests the method when indexes specify an element not in portrait of the target matrix.
        /// </summary>
        [Test]
        public void WhenTargetIndexesSpecifyAnElementNotInPortrait()
        {
            // Arrange
            // Matrix:
            // 1(Y) 0 2(Y) 8
            // 3    4 0    0
            // 5(Y) 0 0(Y) 7
            var target = new CrsSparseMatrix<double>(
                rowsCount: 3,
                columnsCount: 4,
                rowsBegins: new[] { 0, 3, 5, 7 },
                columnsIndexes: new[] { 0, 2, 3, 0, 1, 0, 3 },
                data: new double[] { 1, 2, 8, 3, 4, 5, 7 });

            var targetRowsIndexes = new[] { 0, 2 };
            var targetColumnsIndexes = new[] { 0, 2 };

            var source = new RMDenseMatrix<double>(
                new[]
                {
                    new double[] { 10, 20 },
                    new double[] { 30, 40 }
                });

            // Act and Assert
            Assert.That(
                () => target.Assign(targetRowsIndexes, targetColumnsIndexes, source),
                Throws.TypeOf<ReadOnlyMatrixElementException>().With.Property("ParamName").EqualTo("target"));
        }

        /// <summary>
        ///     Tests the method when target parameter is <see langword="null"/>.
        /// </summary>
        [Test]
        public void WhenTargetIsNull()
        {
            var targetRowsIndexes = new[] { 0, 2 };
            var targetColumnsIndexes = new[] { 0, 3 };

            var source = new RMDenseMatrix<double>(
                new[]
                {
                    new double[] { 10, 20 },
                    new double[] { 30, 40 }
                });

            // Act and Assert
            Assert.That(
                () => CrsSparseMatrixExtensions.Assign(null, targetRowsIndexes, targetColumnsIndexes, source),
                Throws.TypeOf<ArgumentNullException>().With.Property("ParamName").EqualTo("target"));
        }

        /// <summary>
        ///     Tests the method when source parameter is <see langword="null"/>.
        /// </summary>
        [Test]
        public void WhenSourceIsNull()
        {
            // Arrange
            // Matrix:
            // 1 0 2 8
            // 3 4 0 0
            // 5 0 0 7
            var target = new CrsSparseMatrix<double>(
                rowsCount: 3,
                columnsCount: 4,
                rowsBegins: new[] { 0, 3, 5, 7 },
                columnsIndexes: new[] { 0, 2, 3, 0, 1, 0, 3 },
                data: new double[] { 1, 2, 8, 3, 4, 5, 7 });

            var targetRowsIndexes = new[] { 0, 2 };
            var targetColumnsIndexes = new[] { 0, 3 };

            // Act and Assert
            Assert.That(
                () => target.Assign(targetRowsIndexes, targetColumnsIndexes, (RMDenseMatrix<double>)null),
                Throws.TypeOf<ArgumentNullException>().With.Property("ParamName").EqualTo("source"));
        }

        /// <summary>
        ///     Tests the method when targetRowsIndexes parameter is <see langword="null"/>.
        /// </summary>
        [Test]
        public void WhenTargetRowsIndexesCollectionIsNull()
        {
            // Arrange
            // Matrix:
            // 1 0 2 8
            // 3 4 0 0
            // 5 0 0 7
            var target = new CrsSparseMatrix<double>(
                rowsCount: 3,
                columnsCount: 4,
                rowsBegins: new[] { 0, 3, 5, 7 },
                columnsIndexes: new[] { 0, 2, 3, 0, 1, 0, 3 },
                data: new double[] { 1, 2, 8, 3, 4, 5, 7 });

            var targetColumnsIndexes = new[] { 0, 3 };

            var source = new RMDenseMatrix<double>(
                new[]
                {
                    new double[] { 10, 20 },
                    new double[] { 30, 40 }
                });

            // Act and Assert
            Assert.That(
                () => target.Assign(null, targetColumnsIndexes, source),
                Throws.TypeOf<ArgumentNullException>().With.Property("ParamName").EqualTo("targetRowsIndexes"));
        }

        /// <summary>
        ///     Tests the method when targetColumnsIndexes parameter is <see langword="null"/>.
        /// </summary>
        [Test]
        public void WhenTargetColumnsIndexesCollectionIsNull()
        {
            // Arrange
            // Matrix:
            // 1 0 2 8
            // 3 4 0 0
            // 5 0 0 7
            var target = new CrsSparseMatrix<double>(
                rowsCount: 3,
                columnsCount: 4,
                rowsBegins: new[] { 0, 3, 5, 7 },
                columnsIndexes: new[] { 0, 2, 3, 0, 1, 0, 3 },
                data: new double[] { 1, 2, 8, 3, 4, 5, 7 });

            var targetRowsIndexes = new[] { 0, 2 };

            var source = new RMDenseMatrix<double>(
                new[]
                {
                    new double[] { 10, 20 },
                    new double[] { 30, 40 }
                });

            // Act and Assert
            Assert.That(
                () => target.Assign(targetRowsIndexes, null, source),
                Throws.TypeOf<ArgumentNullException>().With.Property("ParamName").EqualTo("targetColumnsIndexes"));
        }

        /// <summary>
        ///     Tests the method when an element of targetRowsIndexes collection is negative or greater than or equal to
        ///     the number of rows of the target matrix.
        /// </summary>
        /// <param name="rowIndex">
        ///     The row index.
        /// </param>
        /// <param name="location">
        ///     The location of the index in the storage.
        /// </param>
        [Test]
        public void WhenRowIndexIsOutOfRange([Values(-1, 3)] int rowIndex, [Values(0, 1)] int location)
        {
            // Arrange
            // Matrix:
            // 1 0 2 8
            // 3 4 0 0
            // 5 0 0 7
            var target = new CrsSparseMatrix<double>(
                rowsCount: 3,
                columnsCount: 4,
                rowsBegins: new[] { 0, 3, 5, 7 },
                columnsIndexes: new[] { 0, 2, 3, 0, 1, 0, 3 },
                data: new double[] { 1, 2, 8, 3, 4, 5, 7 });

            var targetRowsIndexes = new int[2];
            targetRowsIndexes[location] = rowIndex;

            var targetColumnsIndexes = new[] { 0, 3 };

            var source = new RMDenseMatrix<double>(
                new[]
                {
                    new double[] { 10, 20 },
                    new double[] { 30, 40 }
                });

            // Act and Assert
            var argumentName = string.Format(CultureInfo.InvariantCulture, "targetRowsIndexes[{0}]", location);
            Assert.That(
                () => target.Assign(targetRowsIndexes, targetColumnsIndexes, source),
                Throws.TypeOf<ElementIndexIsOutOfRangeException>().With.Property("ParamName").EqualTo(argumentName));
        }

        /// <summary>
        ///     Tests the method when an element of targetColumnsIndexes collection is negative or greater than or equal to
        ///     the number of columns of the target matrix.
        /// </summary>
        /// <param name="columnIndex">
        ///     The column index.
        /// </param>
        /// <param name="location">
        ///     The location of the index in the storage.
        /// </param>
        [Test]
        public void WhenColumnIndexIsOutOfRange([Values(-1, 4)] int columnIndex, [Values(0, 1)] int location)
        {
            // Arrange
            // Matrix:
            // 1 0 2 8
            // 3 4 0 0
            // 5 0 0 7
            var target = new CrsSparseMatrix<double>(
                rowsCount: 3,
                columnsCount: 4,
                rowsBegins: new[] { 0, 3, 5, 7 },
                columnsIndexes: new[] { 0, 2, 3, 0, 1, 0, 3 },
                data: new double[] { 1, 2, 8, 3, 4, 5, 7 });

            var targetRowsIndexes = new[] { 0, 2 };
            var targetColumnsIndexes = new int[2];
            targetColumnsIndexes[location] = columnIndex;

            var source = new RMDenseMatrix<double>(
                new[]
                {
                    new double[] { 10, 20 },
                    new double[] { 30, 40 }
                });

            // Act and Assert
            var argumentName = string.Format(CultureInfo.InvariantCulture, "targetColumnsIndexes[{0}]", location);
            Assert.That(
                () => target.Assign(targetRowsIndexes, targetColumnsIndexes, source),
                Throws.TypeOf<ElementIndexIsOutOfRangeException>().With.Property("ParamName").EqualTo(argumentName));
        }

#endif
    }
}